package com.z.xhelper.core.config;

import android.content.Context;

import com.z.xhelper.core.listener.SocketSendTextMsgListener;
import com.z.xhelper.core.listener.TextMsgReceiveListener;
import com.z.xhelper.core.wx.hook.MsgReceiveHook;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import dalvik.system.DexFile;

public class Reflections {

    private String packageName;

    private List<Class<?>> classList = new ArrayList<>();

    /**
     * 获取应用程序下的所有Dex文件
     *
     * @param context 上下文
     * @return Set<DexFile>
     */
    public static Set<DexFile> applicationDexFile(Context context) {
        return applicationDexFile(context.getPackageCodePath());
    }

    /**
     * 获取应用程序下的所有Dex文件
     *
     * @param packageCodePath 包路径
     * @return Set<DexFile>
     */
    public static Set<DexFile> applicationDexFile(String packageCodePath) {
        Set<DexFile> dexFiles = new HashSet<>();
        File dir = new File(packageCodePath).getParentFile();
        File[] files = dir.listFiles();
        for (File file : files) {
            try {
                String absolutePath = file.getAbsolutePath();
                if (!absolutePath.contains(".")) continue;
                String suffix = absolutePath.substring(absolutePath.lastIndexOf("."));
                if (!suffix.equals(".apk")) continue;
                DexFile dexFile = createDexFile(file.getAbsolutePath());
                if (dexFile == null) continue;
                dexFiles.add(dexFile);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return dexFiles;
    }

    /**
     * 创建DexFile文件
     *
     * @param path 路径
     * @return DexFile
     */
    public static DexFile createDexFile(String path) {
        try {
            return new DexFile(path);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 读取类路径下的所有类
     *
     * @param packageName 包名
     * @param context     包路径
     * @return List<Class>
     */
    public static List<Class<?>> reader(String packageName, Context context) {
        return reader(packageName, context.getPackageCodePath());
    }

    public Reflections(String packageName) {
        this.packageName = packageName;

        // 所有 IHook 的实现类
        this.classList.add(MsgReceiveHook.class);

        // 所有 IListener 的实现类
        this.classList.add(TextMsgReceiveListener.class);
        this.classList.add(SocketSendTextMsgListener.class);

    }

    // 获取指定注解的所有类
    public List<Class<?>> getTypesAnnotatedWith(Class clazz) {
        List<Class<?>> annotatedClasses = new ArrayList<>();
        for (Class<?> aClass : this.classList) {
            if (aClass.isAnnotationPresent(clazz)) {
                annotatedClasses.add(aClass);
            }
        }
        return annotatedClasses;
    }

    // 获取指定接口的所有实现类
    public <T>  List<Class<T>> getSubTypesOf(Class<T> clazz) {
        List<Class<T>> interfaceClasses = new ArrayList<>();
        for (Class<?> aClass : this.classList) {
            if (clazz.isAssignableFrom(aClass) && !aClass.isInterface()) {
                interfaceClasses.add((Class<T>) aClass);
            }
        }
        return interfaceClasses;
    }


    /**
     * 读取类路径下的所有类
     *
     * @param packageName     包名
     * @param packageCodePath 上下文
     * @return List<Class>
     */
    public static List<Class<?>> reader(String packageName, String packageCodePath) {
        List<Class<?>> classes = new ArrayList<>();
        Set<DexFile> dexFiles = applicationDexFile(packageCodePath);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        for (DexFile dexFile : dexFiles) {
            if (dexFile == null) continue;
            Enumeration<String> entries = dexFile.entries();
            while (entries.hasMoreElements()) {
                try {
                    String currentClassPath = entries.nextElement();
                    if (currentClassPath == null || currentClassPath.isEmpty() || currentClassPath.indexOf(packageName) != 0)
                        continue;
                    Class<?> entryClass = Class.forName(currentClassPath, true, classLoader);
                    if (entryClass == null) continue;
                    classes.add(entryClass);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return classes;
    }

}

